package com.yang.mybatis.execute;

import com.google.common.base.CaseFormat;
import com.yang.mybatis.config.MybatisMapperXmlConfiguration;
import com.yang.mybatis.config.MybatisResultMap;
import com.yang.mybatis.config.MybatisResultMapProperty;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.execute.request.MybatisResultParserRequest;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class DefaultMybatisResultParser implements IMybatisResultParser {
    @Override
    public <T> T parseResult(MybatisResultParserRequest mybatisResultParserRequest) throws SQLException {
        ResultSet resultSet = mybatisResultParserRequest.getResultSet();
        if (resultSet == null) {
            return null;
        }
        MybatisSqlStatement mybatisSqlStatement = mybatisResultParserRequest.getMybatisSqlStatement();
        MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration = mybatisResultParserRequest.getMybatisMapperXmlConfiguration();
        int resultTypeCode = parseResultTypeCode(mybatisSqlStatement);
        switch (resultTypeCode) {
            case ONE_COLUMNE:
                return parseResultOfOneColumn(resultSet, mybatisSqlStatement);
            case RESULT_TYPE:
                return parseResultOfResultType(resultSet, mybatisSqlStatement);
        }
        return parseResultOfResultMap(resultSet, mybatisSqlStatement, mybatisMapperXmlConfiguration);
    }

    private <T> T parseResultOfResultMap(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement,
                                         MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration) throws SQLException {
        String resultMap = mybatisSqlStatement.getResultMap();

        MybatisResultMap mybatisResultMap = mybatisMapperXmlConfiguration.getMybatisResultMap(resultMap);

        String classType = mybatisResultMap.getType();
        Map<String, String> fieldName2ColumnNameMap = mybatisResultMap.getProperties()
                .stream()
                .collect(Collectors.toMap(MybatisResultMapProperty::getProperty, MybatisResultMapProperty::getColumn));

        return parseResultOfClassAndFields(resultSet, classType, fieldName2ColumnNameMap);
    }

    private <T> T parseResultOfResultType(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement) throws SQLException {
        String resultType = mybatisSqlStatement.getResultType();

        try {
            Class<?> aClass = Class.forName(resultType);
            Field[] fields = aClass.getDeclaredFields();
            Map<String, String> fieldName2ColumnNameMap = new HashMap<>();
            for (Field field : fields) {
                // 驼峰命名转下划线
                String columnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
                fieldName2ColumnNameMap.put(field.getName(), columnName);
            }

            return parseResultOfClassAndFields(resultSet, resultType, fieldName2ColumnNameMap);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T parseResultOfClassAndFields(ResultSet resultSet, String classType, Map<String, String> fieldName2ColumnNameMap ) throws SQLException {
        try {
            Class<?> aClass = Class.forName(classType);
            Field[] fields = aClass.getDeclaredFields();

            Map<String, Field> fieldName2FieldMap = new HashMap<>();
            for (Field field : fields) {
                fieldName2FieldMap.put(field.getName(), field);
            }

            Object result = aClass.newInstance();
            while (resultSet.next()) {
                for (Map.Entry<String, String> entry : fieldName2ColumnNameMap.entrySet()) {
                    String fieldName = entry.getKey();
                    String columnName = entry.getValue();

                    Object columnValue = resultSet.getObject(columnName);
                    Field field = fieldName2FieldMap.get(fieldName);
                    field.setAccessible(true);
                    field.set(result, columnValue);
                }
            }
            return (T)result;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T parseResultOfOneColumn(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement) throws SQLException {
        return (T) resultSet.getObject(1);
    }

    private int parseResultTypeCode(MybatisSqlStatement mybatisSqlStatement) {
        String resultType = mybatisSqlStatement.getResultType();
        String resultMap = mybatisSqlStatement.getResultMap();
        if (StringUtils.isEmpty(resultType) && StringUtils.isEmpty(resultMap)) {
            return ONE_COLUMNE;
        }
        if (StringUtils.isNotEmpty(resultType) && StringUtils.isNotEmpty(resultMap)) {
            throw new RuntimeException("resultType和resultMap不能同时存在");
        }
        if (StringUtils.isNotEmpty(resultType)) {
            return RESULT_TYPE;
        }
        return RESULT_MAP;
    }
}
